To make an object available to .NET serialization services, all you need to do is decorate each related class (or structure) with the [Serializable] attribute. If you determine that a given type has some member data that should not (or perhaps cannot) participate in the serialization scheme, you can mark such fields with the [NonSerialized] attribute. This can be helpful if you wish to reduce the size of the persisted data, and you have member variables in a serializable class that do not need to be remembered (e.g., fixed values, random -values, and transient data).
To get the ball rolling, create a new Console Application named SimpleSerialize. Insert a new class named Radio, which has been marked [Serializable], excluding a single member variable (radioID) that has been marked [NonSerialized] and will therefore not be persisted into the specified data stream:
[Serializable] public class Radio { public bool hasTweeters; public bool hasSubWoofers; public double[] stationPresets; [NonSerialized] public string radioID = "XF-552RR6"; }
Next, insert two additional class types to represent the JamesBondCar and Car base classes, both of which are also marked [Serializable] and define the following pieces of field data:
[Serializable] public class Car { public Radio theRadio = new Radio(); public bool isHatchBack; } [Serializable] public class JamesBondCar : Car { public bool canFly; public bool canSubmerge; }
Be aware that you cannot inherit the [Serializable] attribute from a parent class. Therefore, if you derive a class from a type marked [Serializable], the child class must be marked [Serializable] as well, or it cannot be persisted. In fact, all objects in an object graph must be marked with the [Serializable] attribute. If you attempt to serialize a nonserializable object using the BinaryFormatter or SoapFormatter, you will receive a SerializationException at runtime.
Notice that in each of these classes, you define the field data as public; this helps keep the example simple. Of course, private data exposed using public properties would be preferable from an OO point of view. Also, for the sake of simplicity, this example does not define any custom constructors on these types; therefore, all unassigned field data will receive the expected default values.
OO design principles aside, you might wonder how the various formatters expect a type’s field data to be defined in order to be serialized into a stream. The answer is that it depends. If you persist an object’s state using the BinaryFormatter or SoapFormatter, it makes absolutely no difference. These types are programmed to serialize all serializable fields of a type, regardless of whether they are public fields, private fields, or private fields exposed through public properties. Recall, however, that if you have points of data that you do not want to be persisted into the object graph, you can selectively mark public or private fields as [NonSerialized], as you do with the string field of the Radio type.
The situation is quite different if you use the XmlSerializer type, however. This type will only serialize public data fields or private data exposed by public properties. Private data not exposed from properties will be ignored. For example, consider the following serializable Person type:
[Serializable] public class Person { // A public field. public bool isAlive = true; // A private field. private int personAge = 21; // Public property/private data. private string fName = string.Empty; public string FirstName { get { return fName; } set { fName = value; } } }
If you processed the preceding with BinaryFormatter or SoapFormatter, you would find that the isAlive, personAge, and fName fields are saved into the selected stream. However, the XmlSerializer would not save the value of personAge because this piece of private data is not encapsulated by a public type property. If you wished to persist the age of the person with the XmlSerializer, you would need to define the field publicly or encapsulate the private member using a public property.